/**
 * Free variable require as specified by
 * http://wiki.commonjs.org/wiki/Modules/Transport/D
 *
 * @TODO: injection
 * @TODO: string factories
 */
var require = (function() {
	var modules = {};
	var deps = [];
	var pathTree = {};
	var basePath = '';
	
	/**
	 * Resolves a relative module id.
	 */
	function resolvePath(target, base) {
		log(target + ' ' + base);
		var newTarget;
		var newBase;
		if ('..' === target.substr(0, 2)) {
			newTarget = target.substr(1);
			newBase = base.split('/');
			newBase.pop();
			return resolvePath(newTarget, newBase.join('/'));
		} else if ('.' === target.substr(0, 1)) {
			return resolvePath(base + target.substr(1), '');
		} else if ('/' === target.substr(0, 1)) {
			return resolvePath(base + target.substr(1), '');
		}

		if (target.indexOf('.') >= 0) {
			newTarget = target.split('/');
			newBase = base.split('/');
			newBase.push(newTarget.shift());
			if (newTarget.length) {
				return resolvePath(newTarget.join('/'), newBase.join('/'));
			}
		}
		return target;
	}
	
	/**
 	* returns the module specified by id
 	* if the id starts with a . then go relative to the base
 	*/
	var require = function(id, base) {

		base = base || '';
		id = resolvePath(id, base);
		
		if (!modules.hasOwnProperty(id)) {
			throw "Unkown module " + id;
		}
		if ('undefined' === typeof modules[id].exports) {
			(function() {
				var exports = {};
				var module = {
					id: id
				};
				modules[id].factory( function(nextId) {
					var newBase = id.split('/');
					newBase.pop();

					return require(nextId, newBase.join('/'));
				}, exports, module);
				modules[id].exports = exports;
			})();
		}
		return modules[id].exports;
	};
	
	/**
 	* resets the internals (used for unit testing)
 	*/
	require.reset = function () {
		modules = {};
		deps = [];
	};
	
	/**
 	* Defines a module by id a definition and a dep list
 	*/
	function defineModule(id, def, deps) {
		var module = {
			id: id,
			injects: ["require", "module", "exports"],
			factory: null,
			deps: deps
		}
		if ('object' === typeof def) {
			for (var prop in def) {
				if (module.hasOwnProperty(prop)) {
					module[prop] = def[prop];
				}
			}
		} else {
			module.factory = def;
		}
		if (null === module.factory) {
			throw "Module must define a factory";
		}
		modules[id] = module;
	};

	/**
 	* Defines a set of modules
 	*/
	require.define = function(def, deps) {
		if ('object' !== typeof def) {
			throw "Definition must be an object";
		}

		for(var id in def) {
			if (def.hasOwnProperty(id)) {
				defineModule(id, def[id], deps);
			}
		}
	};
	
	/**
 	* Resolves the current set of unsatisfied deps
 	*/
	require.resolve = function() {
		var module, id, newDeps = [];
		// for each module push it's dep list to the front of the queue
		for (id in modules) {
			if (modules.hasOwnProperty(id)) {
				module = modules[id];
				// Only add modules we don't already have
				for(var i=0; i < module.deps.length; ++i) {
					if (!modules.hasOwnProperty(module.deps[i])) {
						newDeps.unshift(module.deps[i]);
					}
				}
			}
		}
		deps = newDeps;
	};
	
	/**
 	* Accessor to the first dep in the dep list
 	*/
	require.resolve.next = function() {
		// Locate the next unresolved dependency
		require.resolve();
		if (deps.length) {
			return deps[0];
		} else {
			return undefined;
		}
	};
	
	/**
	 * Registers a path with the resolver
	 */
	require.resolve.path = function(name, path) {
		var parts = name.split('.');
		if (!parts.length) {
			basePath = path;
			return;
		}
		function registerPart(tree, list) {
			// Copy and shift list
			var tmpList = list.slice(0);
			var part = tmpList.shift();
			if ('undefined' === typeof tree[part]) {
				tree[part] = {};
			}

			if (tmpList.length) {
				registerPart(tree[part], tmpList);
			} else {
				tree[part]._path = path;
			}
		}
		
		registerPart(pathTree, parts);
	};
	
	/**
	 * Locate a module from it's name/id
	 */
	require.find = function(name) {
		var parts = name.split('.');
		var path;

		function getPath(tree, defaultPath) {
			var part;
			if (0 === parts.length) {
				return defaultPath + '.js';
			}
			part = parts.shift();
			defaultPath += '/' + part;
			if ('undefined' === typeof tree[part]) {
				return getPath({}, defaultPath);
			}
			if ('undefined' !== typeof tree[part]._path) {
				defaultPath = tree[part]._path;
			}
			return getPath(tree[part], defaultPath);
		}

		path = getPath(pathTree, basePath);

		return path;
	};
	
	return require;
})();
